The High Level Shading Language (HLSL) is the
programming language used to write shaders. Very similar in syntax and
structure to C++, HLSL allows you to create small shader programs that
are loaded onto the video hardware and executed.
In previous versions of Direct3D, shaders were written in a language
that was very much like assembler, which really restricted shader
programming to those few people with a lot of graphics knowledge. The
advent of HLSL opened the world of shaders to a whole new audience and
gave them the tools to create some amazing effects.
Because HLSL is all about shaders, the best
demonstration of the language is in the form of a simple vertex shader.
The small sample below shows a vertex shader as well as a structure
created to hold output variables.
// PS_INPUT - input variables to the pixel shader
// This struct is created and fill in by the
// vertex shader
struct PS_INPUT
{
float4 Pos : SV_POSITION;
float4 Color : COLOR0;
};
////////////////////////////////////////////////
// Vertex Shader - Main Function
///////////////////////////////////////////////
PS_INPUT VS(float4 Pos : POSITION, float4 Color : COLOR)
{
PS_INPUT psInput;
// Pass through both the position and the color
psInput.Pos = mul( Pos, Projection );
psInput.Color = Color;
return psInput;
}
In the following sections, you’ll be given a small introduction to structure and syntax of HLSL.
Note
There are many tools out there that will assist
you in writing HLSL shaders such as Nvidia’s FX Composer and ATI’s
RenderMonkey. Both of these tools can be found on their respective
websites.
Variable Types
HLSL contains many of the variable types that you’ll find in C++ such as int, bool, and float; you’ll also find a few new ones like half, int1x4, and float4.
Because of the specialized hardware that the shader programs run on,
they’re afforded the benefit of new variable types that are optimized
for the shader architecture.
Common variable types are:
bool— Boolean type, holds either true or false.
int, uint— 32-bit signed and unsigned integer.
half— 16-bit value.
float— 32-bit value.
double— 64-bit value.
float2, float3, float4— A packed float type that contains more than one value.
float2x2, float3x3— A two- and three-dimensional matrix.
Some variable types can contain multiple
components allowing you to pack more than a single value into them. For
instance, the variable type float4 allows you to store four
float values within it. By storing values using these specialized types,
the video hardware can optimize access to the data ensuring quicker
access.
float4 tempFloat = float4(1.0, 2.0, 3.0, 4.0);
Any variable that contains multiple components
can have each individual component accessed using swizzling. Swizzling
enables you to split, for instance, a float3 variable into its three components by specifying X, Y, or Z after the variable name. Take a look at the following example; the singleFloat variable is filled with the value found in the newFloat X component.
// Create and fill a float3 variable
float3 newFloat = float3(0.0, 1.0, 2.0);
// Set the variable singleFloat to the value stored in the X component
float singleFloat = newFloat.x;
Any variable containing multiple components can be accessed in this way.
Semantics
Semantics
are a way of letting the shader know what certain variables will be
used for so their access can be optimized. Semantics follow a variable
declaration and have types such as COLOR0, TEXCOORD0, and POSITION. As you can see in the following structure, the two variables Pos and Color are followed by semantics specifying their use.
// PS_INPUT - input variables to the pixel shader
// This struct is created and filled in by the
// vertex shader
struct PS_INPUT
{
float4 Pos : SV_POSITION;
float4 Color : COLOR0;
};
Some commonly used semantics are:
SV_POSITION— A float4 value specifying a transformed position.
NORMAL0— Semantic that is used when defining a normal vector.
COLOR0— Semantic used when defining a color value.
There are many more semantics available; take a look at the HLSL documentation in the DirectX SDK for a complete list.
A lot of semantics end in a numerical value because it is possible to define multiples of those types.
Function Declarations
Functions within HLSL are defined in pretty much the same way they are within other languages.
ReturnValue FunctionName (parameterName : semantic)
{
// function code goes here
}
The function return value can be any of the defined HLSL types, including packed types and void.
When you’re defining a parameter list for a
shader function, it is perfectly valid to specify a semantic following
the variable. There are a few things you need to be aware
of though when defining function parameters. Since HLSL doesn’t have a
specific way for you to return a value by reference within your
parameter list, it defines a few keywords that can be used to achieve
the same results.
Using the out keyword before your
parameter declaration lets the compiler know that the variable will be
used as an output. Additionally, the keyword inout allows the variable to be used both as an input and output.
void GetColor(out float3 color)
{
color = float3(0.0, 1.0, 1.0);
}